import java.io.*;
import java.net.*;
import java.util.*;
import org.xml.sax.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import org.xml.sax.helpers.XMLReaderFactory;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpMethodParams;

class DroidWrapper {

	public Hashtable settings;
	public Hashtable successes = new Hashtable();
	static String conf_file = "droid_control.conf";
	static Hashtable overiders = new Hashtable();
	public String iCal_out = "";
	private String uid = "";
	private int exit_flag = 0;
	DateFormat l_dateFormat = new SimpleDateFormat("yyyyMMdd");
	Date l_date = new Date();
	String part1 = l_dateFormat.format(l_date);
	DateFormat l_dateFormat2 = new SimpleDateFormat("HHmmss");
	String part2 = l_dateFormat2.format(l_date);
	String file_name = part1 + "T" + part2 + "Z";

	private void write_iCal() {
		DateFormat l_dateFormat = new SimpleDateFormat("yyyyMMdd");
		Date l_date = new Date();
		String part1 = l_dateFormat.format(l_date);
		DateFormat l_dateFormat2 = new SimpleDateFormat("HHmmss");
		String part2 = l_dateFormat2.format(l_date);
		String end_date = part1 + "T" + part2 + "Z";
		
		String cal_out = "";
		cal_out += "BEGIN:VEVENT\n";
		if (uid.length()>1) {
			cal_out += "UID:" + uid + "\n";
		}
		cal_out += "DTSTART:"+ file_name + "\n";
		cal_out += "DTEND:"+ end_date + "\n";
		cal_out += "SUMMARY:Droid - Classification Complete\n";
		/*
		 * Get number of objects from URL
		 */
		String url_string = (String)settings.get("web_path") +"droid_" + file_name + ".xml";
		int count = 0;
		try {
			URL url = new URL(url_string);
			BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
			String str;
			
			while ((str = in.readLine()) != null) {
				if (str.indexOf("IdentificationFile Ident") > 0) {
					count++;		
				}
			}
		} catch (Exception ec) {
		}
		String desc_tag = "DESCRIPTION: "+count+" object classified; ";
		if (count > 0) {
			desc_tag += " URL: " + url_string + "; ";
		}
		desc_tag += iCal_out;
		cal_out += reduce(desc_tag);
		cal_out += "ORGANIZER:MAILTO:dct05r@ecs.soton.ac.uk\n";
		cal_out += "STATUS:FINAL\n";
		cal_out += "CLASS:PUBLIC\n";
		cal_out += "END:VEVENT\n";;

		String calendar_url = "";
		String calendar = "";
		calendar_url = (String)settings.get("calendar_server");
		calendar = (String)settings.get("calendar_path");
		System.out.println(calendar_url + calendar);
	
	
		if (!(calendar_url.isEmpty()) && !(calendar.isEmpty())) {
			String cal_server = iCal_to_server(cal_out);
			if (cal_server.equals("true")) {
				successes.put("cal_server","Calendar @ " + calendar_url + calendar + " updated");
			} else {
				successes.put("cal_server","false");
			}
		}
		
		String local_logs = (String)settings.get("local_log");
		if (uid.length() > 0) {
			file_name = uid;
		}
		if (local_logs.substring(local_logs.length()-1,local_logs.length()).equals("/")){} else { local_logs += "/"; }
		if (!(local_logs.isEmpty())) {
			try {
				BufferedWriter out = new BufferedWriter(new FileWriter(local_logs+file_name+".ics",true));
				out.write(cal_out);
				out.close();
				successes.put("local_ics","Local calendar event written (" + local_logs+file_name+".ics)");
			} catch (Exception e) {
				successes.put("local_ics","Failed to write local copy of calendar event");
			} 
		} else {
			successes.put("local_ics","No local path specified");
		}
	}

	private String reduce(String in) {
		char[] letters = in.toCharArray();
		if (letters.length < 75) {
			in += "\n";
			return in;
		} else {
			String ret = "\n";
			String sp = " ";
			char[] newLine = ret.toCharArray();
			char[] space = sp.toCharArray();
			int count =0;
			int first =1;
			String whole = "";
			while (count < letters.length) {
				char[] out_array = new char[75];
				if (first == 1) {
					out_array[0] = letters[0];
					count++;
					first = 0;
				} else {
					out_array[0] = space[0];
				}
				int i=0;
				for (i=1;i<74;i++) {
					try {
						out_array[i] = letters[count];
					} catch (Exception e) {
						break;
					}
					count++;
				}
				out_array[i] = newLine[0];
				whole += String.valueOf(out_array);
			}
			return whole;
		}
	}
	private String iCal_to_server(String cal_out) {
		String success = "true";
		String calendar_url = (String)settings.get("calendar_server");
		String calendar = (String)settings.get("calendar_path");
		
		HttpClient client = new HttpClient();
		
		PutMethod method  = new PutMethod(calendar_url + calendar);
		method.setRequestHeader("Content-Type", "text/icalendar");
		method.setRequestHeader("If-None-Match", "*");
		method.setRequestBody(cal_out);

		// Provide custom retry handler is necessary
		method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
				new DefaultHttpMethodRetryHandler(3, false));

		try {
			// Execute the method.
			int statusCode = client.executeMethod(method);

			if (statusCode < 200 || statusCode > 299) {
				success = "false";
				System.err.println("Method failed: " + method.getStatusLine());
			}

			// Read the response body.
			byte[] responseBody = method.getResponseBody();

			// Deal with the response.
			// Use caution: ensure correct character encoding and is not binary data
			//System.out.println(new String(responseBody));

		} catch (HttpException e) {
			System.err.println("Fatal protocol violation: " + e.getMessage());
			success = "false";
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println("Fatal transport error: " + e.getMessage());
			success = "false";
			e.printStackTrace();
		} finally {
			// Release the connection.
			method.releaseConnection();
		} 
		return success;
	}
	
	DroidWrapper(String UID, int use_calendar){
		uid = UID;
		successes.put("core","true");
		settings = getConfSettings(conf_file);
		if (use_calendar == 0) {
			settings.put("calendar_server","");
			settings.put("calendar_path","");
		}
		settings.put("iCal_timestamp",file_name);
		validateConfSettings("droid_path");
		validateConfSettings("droid_signature_file");
		validateConfSettings("repository_oai_url");
		validateConfSettings("metadataPrefix");
		validateConfSettings("status_file");
		String last_run = "";
		try {
			if ((String)overiders.get("start_date") == null) {
				last_run = getLastRun((String)settings.get("status_file"));
			} else {
				last_run = (String)overiders.get("start_date");
			} 
		} catch (Exception e) {
		}
		// OK now for the fun of fetching the desired format and parsing it!

		String url = (String)settings.get("repository_oai_url") + "?verb=ListRecords&from=" + last_run + "&metadataPrefix=" + (String)settings.get("metadataPrefix");

		String temp_dir = "";
		temp_dir = (String)settings.get("temp_dir");
		if (temp_dir.substring(temp_dir.length()-1,temp_dir.length()).equals("/")){} else { temp_dir += "/"; }
			
		String web_path = "";
		web_path = (String)settings.get("web_path");
		if (web_path.length() > 0) {
			if (web_path.substring(web_path.length()-1,web_path.length()).equals("/")){} else { web_path += "/"; }
		}
		String local_web_path = "";
		try {
			local_web_path = (String)settings.get("local_web_path");
			if (local_web_path.length() > 0) {
				if (local_web_path.substring(local_web_path.length()-1,local_web_path.length()).equals("/")){} else { local_web_path += "/"; }
			}
		} catch (Exception e) {
		}
		String local_logs = "";
		local_logs = (String)settings.get("local_log");
		if (local_logs.substring(local_logs.length()-1,local_logs.length()).equals("/")){} else { local_logs += "/"; }

		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(temp_dir+"droid_"+file_name+".xml"));
			out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			out.newLine();
			out.write("<FileCollection>");
			out.newLine();
			out.close();
		} catch (Exception e) {
			successes.put("core","false");
			successes.put("core_reason","File generation for DROID failed");
			write_log_file();
			System.out.println("FAILED at 1: Exiting");
			System.exit(0);
		}

		try {
			XMLReader parser = XMLReaderFactory.createXMLReader();
			parser.setContentHandler(new XMLContentHandler(settings));
			parser.parse(url);
		} catch (Exception e) {
			successes.put("core","false");
			successes.put("core_reason","Failed to retrieve or parse url for droid import");
			write_log_file();
			System.out.println("FAILED at 2: Exiting");
			System.exit(0);
		}
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(temp_dir+"droid_"+file_name+".xml",true));
			out.write("</FileCollection>");
			out.newLine();
			out.close();
		} catch (IOException e) {
			successes.put("core","false");
			successes.put("core_reason","File generation for DROID failed");
			write_log_file();
			System.out.println("FAILED at 3: Exiting");
			System.exit(0);
		}
		/**
		  * Check that there is something to do, else write this to calendar and exit.
 		  */
		try {
			BufferedReader in = new BufferedReader(new FileReader(temp_dir+"droid_"+file_name+".xml"));
			String str;
			int lines = 0;
			while ((str = in.readLine()) != null) {
				lines++;
			}
			in.close();
			if (lines < 4) {
				exit_flag = 1;
				iCal_out += "No new objects to classify";
				write_iCal();
			} 
		} catch (Exception ec) {
			successes.put("core","false");
			successes.put("core_reason","No droid input file to continue with.");
			write_log_file();
			System.exit(0);
		}
		if (exit_flag != 1) {
			boolean state = invokeDroid();
			successes.put("invoke_droid","true");
			String filetoput = local_logs+file_name+"_classification.xml";
			boolean exists = false;
			while (exists == false) {
				try {
					Thread.sleep(1000);
					exists = (new File(filetoput)).exists();
				} catch (Exception e) {
					e.printStackTrace();
					System.exit(0);
				}
			}
			String putfilename = "droid_" +file_name+".xml";
			if ((boolean)(successes.get("core")).equals("true")) {
				String success = droid_report_to_http(filetoput,putfilename);
				successes.put("core",success);
				successes.put("core_reason","Failed to upload droid output to web path");
			} else {
				write_log_file();
			}
			if ((boolean)(successes.get("core")).equals("true")) {
				write_iCal();
			} else {
				write_log_file();
			}
			if ((boolean)(successes.get("core")).equals("true")) {
				setLastRun((String)settings.get("status_file"));
			} else {
				write_log_file();
			}
		}
		write_log_file();
	}

	private String droid_report_to_http(String filetoput, String putfilename) {
		String success = "true";
		String web_path = "";
		web_path = (String)settings.get("web_path");
		if (web_path.substring(web_path.length()-1,web_path.length()).equals("/")){} else { web_path += "/"; }
		String local_web_path = "";
		try {
			local_web_path = (String)settings.get("local_web_path");
			if (local_web_path.substring(local_web_path.length()-1,local_web_path.length()).equals("/")){} else { local_web_path += "/"; }
		} catch (Exception e) {}
		String protocol = "";
		try {
			if (web_path.substring(0,7).equals("http://")) {
				protocol = "http";
			}
		} catch (Exception e) {}
		if (local_web_path.equals("") && protocol.equals("http")) {
			HttpClient client = new HttpClient();
		
			try {
				PutMethod method  = new PutMethod(web_path + putfilename);
				method.setRequestHeader("Content-Type", "text/xml");
				method.setRequestHeader("If-None-Match", "*");
				method.setRequestBody(new FileInputStream(filetoput));
				// Provide custom retry handler is necessary
				method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
						new DefaultHttpMethodRetryHandler(3, false));

				try {
					// Execute the method.
					int statusCode = client.executeMethod(method);

					if (statusCode < 200 || statusCode > 299) {
						success = "false";
						System.err.println("Method failed: " + method.getStatusLine());
					}

					// Read the response body.
					byte[] responseBody = method.getResponseBody();

					// Deal with the response.
					// Use caution: ensure correct character encoding and is not binary data
					//System.out.println(new String(responseBody));

				} catch (HttpException e) {
					System.err.println("Fatal protocol violation: " + e.getMessage());
					success = "false";
					e.printStackTrace();
				} catch (IOException e) {
					System.err.println("Fatal transport error: " + e.getMessage());
					success = "false";
					e.printStackTrace();
				} finally {
					// Release the connection.
					method.releaseConnection();
				} 
			} catch (FileNotFoundException wioe) {
				success= "false";
			}
		} else {
			if (!local_web_path.equals("")) {
				web_path = local_web_path;
			}
			try {
				BufferedReader in = new BufferedReader(new FileReader(filetoput));
				BufferedWriter out = new BufferedWriter(new FileWriter(web_path+putfilename));
				String str;
				while ((str = in.readLine()) != null) {
					out.write(str);
					out.newLine();	
				}
				in.close();
				out.close();		
			} catch (Exception ioe) {
				ioe.printStackTrace();
				success = "false";
			}
		}
		return success;	
	}

	private void write_log_file() {
		String local_logs = (String)settings.get("local_log");
		if (local_logs.substring(local_logs.length()-1,local_logs.length()).equals("/")){} else { local_logs += "/"; }
		if (!(local_logs.isEmpty())) {
			try {
				BufferedWriter out = new BufferedWriter(new FileWriter(local_logs+file_name+".log"));
				System.out.println("Writing to log file " + local_logs+file_name+".log");
				Enumeration k = successes.keys();
				while (k.hasMoreElements()) {
					String key = (String) k.nextElement();
					out.write(key + ": " + (String) successes.get(key));
					out.newLine();
				}
				out.close();
			} catch (Exception e) {
				System.out.println("Cannot even write a log file... giving up and dieing!");
				System.exit(0);
			}
		}
		
	}

	private String getDate() {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date date = new Date();
		return dateFormat.format(date);
	}

	private boolean invokeDroid() {
		//System.out.println(uri);
		
		String path = ".";	
		File dir1 = new File(".");
		try {
			path = dir1.getCanonicalPath();
		} catch (Exception e) {
			e.printStackTrace();
		}

		/**
		  * Check file for some files to identify
		  */

		boolean items=false;
		
		String temp_dir = "";
		temp_dir = (String)settings.get("temp_dir");
		if (temp_dir.substring(temp_dir.length()-1,temp_dir.length()).equals("/")){} else { temp_dir += "/"; }
		
		if (!(temp_dir.substring(0,1).equals("/"))) {
			temp_dir = path + "/" + temp_dir;
		}
	
		String local_logs = "";
		local_logs = (String)settings.get("local_log");
		if (local_logs.substring(local_logs.length()-1,local_logs.length()).equals("/")){} else { local_logs += "/"; }
		
		if (!(local_logs.substring(0,1).equals("/"))) {
			local_logs = path + "/" + local_logs;
		}
		
		String file = temp_dir + "droid_"+file_name+".xml";
		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			while ((str = in.readLine()) != null) {
				if (str.indexOf("IdentificationFile") > -1) {
					items = true;
				}
			}
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (items) {
			String[] args = new String[4];
			args[0] = "-A" + temp_dir + "droid_" + file_name+".xml";
			args[1] = "-O"+local_logs+file_name+"_classification";
			args[2] = "-FXML";
			args[3] = "-S" + (String)settings.get("droid_path") + "/" + (String)settings.get("droid_signature_file");
			try {
				uk.gov.nationalarchives.droid.AnalysisController.main(args);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return true;	
	}

	private int droidProcess(String line) {
		System.out.println(line);
		return 200;
	}
	
	private void setLastRun(String status_file) {
		String last_run = getDate();
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(status_file));
			out.write(last_run);
			out.close();
		} catch (IOException e) {
			successes.put("core","false");
			successes.put("core_reason","CRITICAL: Failed to write to last_run file!");
		}
	}
	

	private String getLastRun(String status_file) {
		String last_run = "0000-00-00";
		try {
			BufferedReader in = new BufferedReader(new FileReader(status_file));
			last_run = in.readLine();
			in.close();
		} catch (IOException e) {
		}
		return last_run;	
	}
	
	private void validateConfSettings(String variable) {
		try {
			if (settings.get(variable)==null) {
				System.out.println("Error: Configuration Variable " + variable + " undefined");
				System.exit(0);
			} 
		} catch (Exception e) {
			System.out.println("Error: Configuration Variable " + variable + " undefined");
			System.exit(0);
		}
	}
	
	private Hashtable getConfSettings(String conf_file) {
		settings = new Hashtable();
		try {
			BufferedReader in = new BufferedReader(new FileReader(conf_file));
			String line;
			while ((line = in.readLine()) != null) {
				line = line.trim();
				if (!line.equals("")) {
					if (!line.substring(0,1).equals("#")) {
						String[] sline = line.split("=");
						try { 
							String slineout = "";
							try {
								for(int i=1;i<sline.length;i++) {
									slineout += sline[i];
								}
							} catch (Exception e) {}
							sline[1] = slineout;
							settings.put(sline[0].trim(),sline[1].trim());
						} catch (ArrayIndexOutOfBoundsException a) {
							settings.put(sline[0].trim(),"");
						}
					}
				}
			}
			in.close();
		} catch (IOException e) {
			System.out.println("Failed to parse log file.");
		}
		return settings;
	} 

	public static void main(String[] args){
		try {
			if (args[0].equals("--help")) {
				System.out.println("Droid Wrapper (v0.2)");
				System.out.println("");
				System.out.println("Usage:");
				System.out.println("");
				System.out.println(" -c    Path to Configuration File");
				System.out.println("");
				System.out.println(" -d    Start Date for DROID parse (overides last run date)");
				System.out.println("");
				System.out.println("");
				System.exit(0);
			} else {
				while (args.length > 0) {
					if (args[0].equals("-c")) {
						conf_file = args[1];
					}
					if (args[0].equals("-d")) {
						overiders.put("start_date",args[1]);
					}
					if (args.length > 2) {
						String[] foo = new String[args.length-2];
						for (int i=2;i<args.length;i++) {
							foo[i-2] = args[i];
						}
						args = foo;
					} else {
						args = new String[0];
					}
				}
			}
		} catch (Exception e) {
		}
		DroidWrapper dw = new DroidWrapper("",1);
	}
}
